/*!
 * # [460.LFU缓存](https://leetcode.cn/problems/lfu-cache/description/)
 *
 * @lc app=leetcode.cn id=460 lang=rust
 *
 * ## 难度
 * Hard (43.44%)
 * Likes:    481
 * Dislikes: 0
 * Total Accepted:    37.9K
 * Total Submissions: 87.2K
 * Testcase Example:  '["LFUCache","put","put","get","put","get","get","put","get","get","get"]\n' + '[[2],[1,1],[2,2],[1],[3,3],[2],[3],[4,4],[1],[3],[4]]'
 *
 * ## 难度
 *
 * 请你为 最不经常使用（LFU）缓存算法设计并实现数据结构。
 *
 * 实现 LFUCache 类：
 *
 * ```cpp
 * LFUCache(int capacity); // 用数据结构的容量 capacity 初始化对象
 * int get(int key); // 如果键 key 存在于缓存中，则获取键的值，否则返回 -1 。
 * void put(int key, int value); //  如果键 key 已存在，则变更其值；
 *                               // 如果键不存在，请插入键值对。当缓存达到其容量capacity 时，则应该在插入新项之前，移除最不经常使用的项。
 *                               // 在此问题中，当存在平局（即两个或更多个键具有相同使用频率）时，
 *                               // 应该去除 最近最久未使用的键。
 * ```
 *
 * 为了确定最不常使用的键，可以为缓存中的每个键维护一个 使用计数器 。使用计数最小的键是最久未使用的键。
 *
 * 当一个键首次插入到缓存中时，它的使用计数器被设置为 1 (由于 put 操作)。对缓存中的键执行 get 或 put 操作，使用计数器的值将会递增。
 *
 * 函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。
 *
 *
 *
 * ## 示例：
 * - 输入：
 * ["LFUCache", "put", "put", "get", "put", "get", "get", "put", "get", "get", "get"]
 * [[2], [1, 1], [2, 2], [1], [3, 3], [2], [3], [4, 4], [1], [3], [4]]
 * - 输出：
 * [null, null, null, 1, null, -1, 3, null, -1, 3, 4]
 * - 解释：
 * ```cpp
 * // cnt(x) = 键 x 的使用计数
 * // cache=[] 将显示最后一次使用的顺序（最左边的元素是最近的）
 * LFUCache lfu = new LFUCache(2);
 * lfu.put(1, 1);   // cache=[1,_], cnt(1)=1
 * lfu.put(2, 2);   // cache=[2,1], cnt(2)=1, cnt(1)=1
 * lfu.get(1);      // 返回 1
 *                  // cache=[1,2], cnt(2)=1, cnt(1)=2
 * lfu.put(3, 3);   // 去除键 2 ，因为 cnt(2)=1 ，使用计数最小
 *                  // cache=[3,1], cnt(3)=1, cnt(1)=2
 * lfu.get(2);      // 返回 -1（未找到）
 * lfu.get(3);      // 返回 3
 *                  // cache=[3,1], cnt(3)=2, cnt(1)=2
 * lfu.put(4, 4);   // 去除键 1 ，1 和 3 的 cnt 相同，但 1 最久未使用
 *                  // cache=[4,3], cnt(4)=1, cnt(3)=2
 * lfu.get(1);      // 返回 -1（未找到）
 * lfu.get(3);      // 返回 3
 *                  // cache=[3,4], cnt(4)=1, cnt(3)=3
 * lfu.get(4);      // 返回 4
 *                  // cache=[3,4], cnt(4)=2, cnt(3)=3
 * ```
 *
 * ## 提示：
 * - 0 <= capacity <= 10^4
 * - 0 <= key <= 10^5
 * - 0 <= value <= 10^9
 * - 最多调用 2 * 10^5 次 get 和 put 方法
 */

// @lc code=start

use std::{
    collections::{BTreeSet, HashMap},
    convert::TryInto,
};
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
struct CacheEntry {
    count: usize,     //访问计数
    used_time: usize, //最新访问时间
    key: i32,
    value: i32,
}
/**
 * Your LFUCache object will be instantiated and called as such:
 * let obj = LFUCache::new(capacity);
 * let ret_1: i32 = obj.get(key);
 * obj.put(key, value);
 */
struct LFUCache {
    entry_map: HashMap<i32, CacheEntry>,  // key -> entry
    sorted_entries: BTreeSet<CacheEntry>, // sorted entries
    cap: usize,
    time: usize,
}

/**
 * `&self` means the method takes an immutable reference.
 * If you need a mutable reference, change it to `&mut self` instead.
 */
impl LFUCache {
    fn new(capacity: i32) -> Self {
        Self {
            cap: capacity.try_into().unwrap(),
            time: 0,
            entry_map: HashMap::new(),
            sorted_entries: BTreeSet::new(),
        }
    }

    /// get
    fn get(&mut self, key: i32) -> i32 {
        self.time += 1;
        match self.entry_map.get_mut(&key) {
            None => -1,
            Some(entry) => {
                self.sorted_entries.remove(entry);
                entry.count += 1;
                entry.used_time = self.time;
                self.sorted_entries.insert(entry.clone());

                entry.value
            }
        }
    }

    fn put(&mut self, key: i32, value: i32) {
        self.time += 1;
        if self.cap < 1 {
            return;
        }

        match self.entry_map.get_mut(&key) {
            Some(entry) => {
                self.sorted_entries.remove(entry);
                entry.count += 1;
                entry.used_time = self.time;
                entry.value = value;
                self.sorted_entries.insert(entry.clone());
            }
            None => {
                // 如果cache满了
                if self.is_full() {
                    // 删除最不常用的元素
                    let entry = self.sorted_entries.iter().next().unwrap().clone();
                    self.sorted_entries.remove(&entry);
                    self.entry_map.remove(&entry.key);
                }

                // 插入entry
                let entry = CacheEntry {
                    key,
                    value,
                    count: 1,
                    used_time: self.time,
                };
                self.entry_map.entry(key).or_insert(entry.clone());
                self.sorted_entries.insert(entry);
            }
        }
    }

    fn is_full(&self) -> bool {
        self.entry_map.len() >= self.cap
    }
}

// @lc code=end
